<?php

namespace liketp;

use Closure;
use liketp\foundation\Pipeline;

class Middleware
{
    protected $app;
    protected $wareList = [];
    protected $pipeType = 'onion'; // onion | cowpea

    public function __construct(App $app, ?string $pipeType = '')
    {
        $this->app = $app;
        if (in_array($pipeType, ['onion', 'cowpea'])) $this->pipeType = $pipeType;
    }

    public function add($ware, string $scope = 'global', $params = [])
    {
        $params = is_array($params) ? $params : [$params];
        $list = $this->wareList[$scope] ?? [];
        array_push($list, [$ware, $params]);
        $this->wareList[$scope] = $list;
        return $this;
    }

    public function multipleAdd(array $wareList, string $scope = 'global')
    {
        foreach ($wareList as $ware) {
            if (is_array($ware)) {
                if (isset($ware[0])) {
                    $this->add($ware[0], $scope, $ware[1] ?? []);
                }
            } else {
                $this->add($ware, $scope);
            }
        }
        return $this;
    }

    public function removeList(string $scope)
    {
        $this->wareList[$scope] = [];
        return $this;
    }

    public function pipeline($scope)
    {
        $wares = $this->wareList[$scope] ?? [];

        $formatedList = array_map(function ($ware) {
            $wareItem = $this->formatWare($ware, $this->pipeType);
            return $wareItem;
        }, $wares);
        $wareList = array_filter($formatedList);

        $pipeline = new Pipeline($this->pipeType);
        $pipeline->through($wareList);

        return $pipeline;
    }


    //------------------------------------------------ protected
    protected function formatWare($ware, $pipeType)
    {
        $fn = $this->pickFn($ware);
        $args = $this->pickArgs($ware);
        $wareItem = [];

        if ($pipeType == 'onion') {
            $fn = $this->formatCallable($fn, 'handle');

            if ($fn) {
                $wareItem = [
                    'fn' => $fn,
                    'args' => $args,
                ];
            }
        } else if ($pipeType == 'cowpea') {
            $fns = $this->formatCallablePair($fn, ['before', 'after']);

            if (empty($fns[0]) == false) {
                $wareItem['before'] = [
                    'fn' => $fns[0],
                    'args' => $args
                ];
            }
            if (empty($fns[1]) == false) {
                $wareItem['after'] = [
                    'fn' => $fns[1],
                    'args' => $args
                ];
            }
        }

        $wareItem = empty($wareItem) ? null : $wareItem;

        return $wareItem;
    }
    protected function formatCallable($ware, $defaultFn)
    {
        // function
        if ($ware instanceof Closure) {
            return $ware;
        } else if (is_string($ware)) {
            // static::method
            if (strpos($ware, '::') > 0) {
                return $ware;
            }
            // alias or class full name. example: http, authToken, app\middleware\Csrf
            else {
                return [app($ware), $defaultFn];
            }
        }
        // [class|obj, method]
        else if (is_array($ware)) {
            if (count($ware) == 1) {
                $class = $ware[0];
                $method = $defaultFn;
            } else if (count($ware) > 1) {
                [$class, $method] = $ware;
            } else {
                return null;
            }
            $class = is_string($class) ? app($class) : $class;
            return [$class, $method];
        }
        // obj
        else if (is_object($ware)) {
            return [$ware, $defaultFn];
        }
    }

    protected function formatCallablePair($callable, array $defaultFnPair): array
    {
        $fn0 = $fn1 = null;

        if ($callable instanceof Closure) {
            $fn0 = $callable;
        } else if (is_array($callable)) {
            if (empty($callable[0]) == false) {
                $fn0 = $this->formatCallable($callable[0], $defaultFnPair[0]);
            }
            if (empty($callable[1]) == false) {
                $fn1 = $this->formatCallable($callable[1], $defaultFnPair[1]);
            }
        } else if (is_string($callable)) {
            if (strpos($callable, '::') > 0) {
                $fn0 = $callable;
            } else {
                $fn0 = $this->formatCallable($callable, $defaultFnPair[0]);
                $fn1 = $this->formatCallable($callable, $defaultFnPair[1]);
            }
        } else if (is_object($callable)) {
            $fn0 = [$callable, $defaultFnPair[0]];
            $fn1 = [$callable, $defaultFnPair[1]];
        }

        return [$fn0, $fn1];
    }

    protected function pickFn($wareItem)
    {
        if (is_array($wareItem)) {
            return $wareItem[0] ?? null;
        } else {
            return null;
        }
    }

    protected function pickArgs($wareItem)
    {
        if (is_array($wareItem)) {
            return $wareItem[1] ?? [];
        } else {
            return [];
        }
    }
}
